package com.google.code.flexmetavalidator.metavalidation
{
	import com.google.code.flexmetavalidator.reflection.MetaParameter;
	import com.google.code.flexmetavalidator.reflection.MetaUtils;
	import com.google.code.flexmetavalidator.reflection.VariableInfo;
	
	import flash.net.getClassByAlias;
	import flash.utils.Dictionary;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	import mx.collections.ArrayCollection;
	import mx.collections.IList;

	public class MetaValidator
	{
		[Bindable]
		public var errors : ArrayCollection;
		
		public var lastResult : Boolean;
		private var validationMetaName : String; 
		private var validationTypeName : String;
		
		[ArrayElementType("cz.yphone.metavalidation.ValidationTest")] 
		private var validationTests : Array;
		
		/**
		 * Constructor
		*/
		public function MetaValidator(validationMetaName : String = "Validate", validationTypeName : String = "type")
		{
			errors = new ArrayCollection();
			validationTests = new Array();
			this.validationMetaName = validationMetaName;
			this.validationTypeName = validationTypeName;
			addDefaultTests();
		}
				
		/**
		 * Main Validation method. Returns true if the object is valid.
		 * Results are stored in errors array;
		 */
		public function validate(object : Object, resetErrors : Boolean = true) : Boolean
		{
			errors = resetErrors?new ArrayCollection():errors;
			lastResult = true;
			var className : String = getQualifiedClassName(object);
			var cls : Class = getDefinitionByName(className) as Class;
			for each (var test : ValidationTest in validationTests)
			{
				var partialResult : Boolean = validateObject(object, cls, validationMetaName, validationTypeName, test.type, test.testFunction, test.errorString);
				lastResult = lastResult && partialResult;
			}
			return lastResult;
		}
		
		public function addTest(validationTest : ValidationTest) : void
		{
			validationTests.push(validationTest);
		}
		
		/**
		 * Method which adds default set of validators.
		 */
		protected function addDefaultTests() : void
		{
			// Add tests which we want to run
			addTest(new ValidationTest("notNull", ValidationTests.notNull, "Value must not be null"));
			addTest(new ValidationTest("notEmpty", ValidationTests.notEmpty, "Value must not be empty"));
			addTest(new ValidationTest("notZero", ValidationTests.notZero, "Value must not be 0"));
			addTest(new ValidationTest("isNumeric", ValidationTests.isNumeric, "Value must be numeric"));
			addTest(new ValidationTest("length", ValidationTests.length, "Length is greater or smaller than expected or it cannot be measured"));
			addTest(new ValidationTest("numericTest", ValidationTests.numericTest, "Value is greater or smaller than expected"));
			addTest(new ValidationTest("listNotEmpty", ValidationTests.listNotEmpty, "List is empty"));	
		}
		
		/**
		 * Main method of the validator. Identifies the variables and methods which are marked for the validation, 
		 * have the specified type and value of the metadata.
		 * After identifying, runs the tets on them and return true, if all passed.
		 * False if any of them fails.
		 */
		private function validateObject(object : Object, cls : Class, metaName : String, metaType : String, metaTypeValue : String, testFunction : Function, testErrorString : String) : Boolean
		{
			var isValid : Boolean = true;
			var variables : Array = MetaUtils.getElementsByMeta(cls, metaName, metaType, metaTypeValue);
			for each (var variable : VariableInfo in variables)
			{
				// Check if the property exists
				var variableName : String = variable.name;
				try 
				{
					var varValue : * = object[variableName];
					
					// If the value is IList, we run validation recursivelly
					if (varValue is IList || varValue is Array)
					{
						for each (var listItem : Object in varValue)
						{
							var recResult : Boolean = validate(listItem, false);
							isValid = isValid && recResult;
						}
					}
					
					var testFunctionArguments : int = testFunction.length;
					var metaParamsDict : Dictionary = variable.metaTagsDictionary[metaName].metaParametersDictionary;
					
					switch(testFunctionArguments)
					{
						// In case there is only one argument in the test function, we pass parameterValue (property)
						case 1:
							if (!testFunction(varValue))
							{
								isValid = false;
								errors.addItem(cls + ": " + variableName + ": " + testErrorString + " (parameter value = " + varValue + ")");
							}
							break;
						
						// In case there are two arguments in the test function, we pass parameterValue and extra "value" parameter from metadata
						// As an example - [Validation(type="size", value="5")]
						case 2:
							var valueArg : MetaParameter = metaParamsDict["value"] as MetaParameter;
							
							var additionalValue : Object;
							if (valueArg != null) 
								additionalValue = valueArg.value; 

							if (!testFunction(varValue, additionalValue))
							{
								isValid = false;
								errors.addItem(cls + ": " + variableName + ": " + testErrorString + " (parameter value = " + varValue + ", value = " + additionalValue +")");
							}
							break;
						
						// If there are 3 parameters in the test function, we pass
						// parameterValue (property value), "min" from metadata, "max" from metadata
						// Example - [Validation(type="length",min=5, max=10)]
						case 3:
							var minArg : MetaParameter = metaParamsDict["min"] as MetaParameter;
							var maxArg : MetaParameter = metaParamsDict["max"] as MetaParameter;
							var minValue : Object = null;
							var maxValue : Object = null;
							if (minArg != null) minValue = minArg.value;
							if (maxArg != null) maxValue = maxArg.value;
							
							
							if (!testFunction(varValue, minValue, maxValue))
							{
								isValid = false;
								errors.addItem(cls + ": " + variableName + ": " + testErrorString + " (parameter value = " + varValue + ", expected min = " + minValue + ", expected max = " + maxValue +")");
							}
							break;
					}
				}
				catch (e : Error)
				{
					isValid = false;
					errors.addItem(variableName + " - Problem when validating - " + e.message);
					throw e;
				}
			}
			
			return isValid;
		}		
	}
}